package io.renren.wechat.controller;

import io.renren.wechat.config.WechatProperties;
import io.renren.wechat.service.contact.UserService;
import io.renren.wechat.service.suite.SuiteService;
import io.renren.wechat.service.suite.dto.UserClientInfoResponse;
import io.renren.wechat.support.RedirectUriUtils;
import io.renren.wechat.support.ServletRequestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.PostConstruct;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.concurrent.TimeUnit;

/**
 * 调用流程为：
 * A) 用户访问第三方服务，第三方服务通过构造OAuth2链接（参数包括当前第三方服务的身份ID，以及重定向URI），将用户引导到认证服务器的授权页
 * B) 用户选择是否同意授权
 * C) 若用户同意授权，则认证服务器将用户重定向到第一步指定的重定向URI，同时附上一个授权码。
 * D) 第三方服务收到授权码，带上授权码来源的重定向URI，向认证服务器申请凭证。
 * E) 认证服务器检查授权码和重定向URI的有效性，通过后颁发AccessToken（调用凭证）
 *
 * @author dmcp@qq.com
 * @since 2021/11/23 19:51
 */
@Controller
@RequestMapping("/app")
public class WechatAuth2RedirectController {
    private static final Logger LOGGER = LoggerFactory.getLogger(WechatAuth2RedirectController.class);
    private static final String X_AGENTID_HEADER_NAME = "agentId";
    private static final String X_CORPID_HEADER_NAME = "corpId";
    private static final String X_USERTYPE_HEADER_NAME = "userType";

    private static final String COOKIE_DOMAIN_NAME = "zijisz.com";
    private static final int COOKIE_MAX_AGE = 60 * 60 * 24 * 30;  // 存在时间，单位为秒，下边表示存1周
    private String appH5IndexUri = "https://www.zijisz.com/#/";
    private String appH5SeatSubscribeUri = "https://www.zijisz.com/h5/#/pages/seat/book-seat/book-seat";
    private String appH5RoomSubscribeUri = "https://www.zijisz.com/#/roomDetail";

    private static final String REDIRECT_IN_URI = "https://www.zijisz.com/qywx/app/main";

    private static final String REDIRECT_URI_TEMPLATE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&state=%s#wechat_redirect";
    private static final String REDIRECT_URI_USER_TEMPLATE = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=%s&scope=%s&agentid=%s&state=%s#wechat_redirect";

    private WechatProperties wechatProperties;
    private String redirectUri;
    private SuiteService suiteService;
    private UserService userService;
    /* 从企业微信App进入的 */
    private static final String REQUEST_SOURCE_QYWX_APP = "app";
    /* 从企业微信扫码进入的 */
    private static final String REQUEST_SOURCE_SCAN_QRCODE = "scan";


    @PostConstruct
    public void init() {
        try {
            appH5IndexUri = wechatProperties.getRedirectH5IndexUrl();
            appH5SeatSubscribeUri = wechatProperties.getRedirectH5SeatSubscribeUrl();
            appH5RoomSubscribeUri = wechatProperties.getRedirectH5RoomSubscribeUrl();

            LOGGER.info("using redirectUri: {}", appH5IndexUri);
            LOGGER.info("using subscribeSeatUri: {}", appH5SeatSubscribeUri);
            LOGGER.info("using subscribeRoomUri: {}", appH5RoomSubscribeUri);
        } catch (Exception e) {
            LOGGER.error("invalid redirect url: {}", REDIRECT_URI_TEMPLATE);
        }
    }

    @RequestMapping("/index")
    public ResponseEntity<String> redirectIn(HttpServletRequest request) {
        final String header = request.getHeader(X_CORPID_HEADER_NAME);
        ServletRequestUtils.printRequestCookies(request);

        String requestSource = request.getParameter("type");
        /* agentId 可能为空 */
        String agentId = request.getParameter("agentId");

        LOGGER.info(">>>>>>>>request_header_index_req: [{}] value: [{}]", X_CORPID_HEADER_NAME, header);
        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("request to /qywx_app_index parameters: ");
            ServletRequestUtils.printRequestCookies(request);
            ServletRequestUtils.printRequestParameters(request);
        }

        try {
            if (StringUtils.isBlank(requestSource)) {
                requestSource = REQUEST_SOURCE_QYWX_APP;
            }

            if (StringUtils.equalsIgnoreCase(requestSource, REQUEST_SOURCE_SCAN_QRCODE)) {
                if (StringUtils.isBlank(agentId)) {
                    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("agentId is required for type=scan");
                }
            }

            redirectUri = RedirectUriUtils.buildRedirectUri(REDIRECT_IN_URI, REDIRECT_URI_TEMPLATE, wechatProperties.getSuiteId(), request);

            LOGGER.info(">>>>>build new redirectUri: {}", redirectUri);
        } catch (UnsupportedEncodingException e) {
            LOGGER.error("invalid requestUri: {}", redirectUri, e);
            throw new RuntimeException(e);
        }

        return ResponseEntity.status(HttpStatus.FOUND).location(URI.create(redirectUri)).build();
    }

    /**
     * @param code  通过成员授权获取到的code，最大为512字节。每次成员授权带上的code将不一样，code只能使用一次，5分钟未被使用自动过期。
     * @param state state
     */
    @RequestMapping("/main")
    public ResponseEntity<String> redirectTo(@RequestParam("code") String code,
                                             @RequestParam("state") String state,
                                             @RequestParam(name = "agentId", required = false) String agentId,
                                             HttpServletRequest request,
                                             HttpServletResponse response) throws IOException {

        LOGGER.debug("redirectIn request params code: {}, state: {}, agentId: {}", code, state, agentId);
        final String header = request.getHeader(X_CORPID_HEADER_NAME);
        LOGGER.info(">>>>>>>>request_header in /main request: [{}] value: [{}]", X_CORPID_HEADER_NAME, header);

        if (LOGGER.isDebugEnabled()) {
            ServletRequestUtils.printRequestCookies(request);
            ServletRequestUtils.printRequestParameters(request);
        }

        String h5IndexUri = "";
        if (StringUtils.equalsIgnoreCase(request.getParameter("type"), REQUEST_SOURCE_SCAN_QRCODE)) {
            //扫码进来的，不查询用户信息（目前通过code查询报40029 invalid code，不知原因），前端预订也不需要用户信息和corp信息
            if (StringUtils.isNotBlank(request.getParameter("id"))) {
                h5IndexUri = RedirectUriUtils.buildH5SubscribeUri(appH5SeatSubscribeUri, "",
                        "2", "", "", "", request);
                h5IndexUri += "&type=" + REQUEST_SOURCE_SCAN_QRCODE;
            }

            LOGGER.debug("redirecting to h5 index url: {}", h5IndexUri);

            return ResponseEntity.status(HttpStatus.FOUND).location(URI.create(h5IndexUri))
                    .header(X_CORPID_HEADER_NAME, "")
                    .header(X_USERTYPE_HEADER_NAME, "2")
                    .build();
        }

        String suiteToken;
        final UserClientInfoResponse clientInfo;
        if ("123457".equals(state)) {
            //第二个应用
            clientInfo = suiteService.getClientInfo2(code);
            suiteToken = wechatProperties.getSuiteToken2();
        } else {
            clientInfo = suiteService.getClientInfo(code);
            suiteToken = wechatProperties.getSuiteToken();
        }

        LOGGER.info(">>>>>user client info: {} is coming our app!", clientInfo);

        final String corpId = clientInfo.getCorpId();
        final String userId = clientInfo.getUserId();

        String agentIdOfUserIsVisiting;
        if (StringUtils.isNotBlank(agentId)) {
            agentIdOfUserIsVisiting = agentId;
        } else {
            agentIdOfUserIsVisiting = suiteService.getVisitingAgentIdFromCache(corpId, userId, suiteToken);
        }

        try {
            int loopTimes = 3;
            while (StringUtils.isBlank(agentIdOfUserIsVisiting) && loopTimes > 0) {
                LOGGER.debug(">>>>retry getting agentId from cache with corpId: {}, userId: {}", corpId, userId);
                TimeUnit.SECONDS.sleep(1);
                loopTimes -= 1;
                agentIdOfUserIsVisiting = suiteService.getVisitingAgentIdFromCache(corpId, userId, suiteToken);
            }
        } catch (InterruptedException e) {
            LOGGER.error("get agentId failed.", e);
        }

//        if (StringUtils.equalsIgnoreCase(type, REQUEST_SOURCE_SCAN_QRCODE)) {
//            agentIdOfUserIsVisiting = agentId;
//        }

        if (StringUtils.isBlank(agentIdOfUserIsVisiting)) {
            agentIdOfUserIsVisiting = ServletRequestUtils.getCookieValue(request, "agentId");
        }

        LOGGER.debug("!!!userId: {} corpId: {} is visiting agentId: {}", userId, corpId, agentIdOfUserIsVisiting);

        if (StringUtils.isBlank(agentIdOfUserIsVisiting)) {
            LOGGER.info("agentId is blank");
            // return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("agentId is required, type=" + type);
            //ios 乱码，改编码格式？或英文
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Please enter from wecom");
        }

        Cookie corpCookie = buildCookie(X_CORPID_HEADER_NAME, corpId);
        Cookie agentIdCookie = buildCookie(X_AGENTID_HEADER_NAME, agentIdOfUserIsVisiting);

        final boolean isAdminUser = userService.isAdminUser(corpId, userId, agentIdOfUserIsVisiting, state, suiteToken);
        String userType;
        if (isAdminUser) {
            userType = String.valueOf(UserService.ADMIN_USER_TYPE);
        } else {
            userType = String.valueOf(UserService.NORMAL_USER_TYPE);
        }
        Cookie userTypeCookie = buildCookie(X_USERTYPE_HEADER_NAME, userType);

        response.addCookie(corpCookie);
        response.addCookie(userTypeCookie);
        response.addCookie(agentIdCookie);

        String openId;
        try {
            openId = userService.userId2openId(corpId, userId, state);
        } catch (Exception e) {
            LOGGER.error("get openId failed with userId: {}", userId, e);
            openId = userId;
        }

        String suitId = wechatProperties.getSuiteId();
        if ("123457".equals(state)) {
            suitId = wechatProperties.getSuiteId2();
        }

        h5IndexUri = RedirectUriUtils.buildH5IndexUri(appH5IndexUri, corpId, userType,
                userId, openId, agentIdOfUserIsVisiting, suitId);
//        if (StringUtils.equalsIgnoreCase(request.getParameter("type"), REQUEST_SOURCE_SCAN_QRCODE)) {
//            if (StringUtils.isNotBlank(request.getParameter("id"))) {
//                h5IndexUri = RedirectUriUtils.buildH5SubscribeUri(appH5SeatSubscribeUri, corpId,
//                        userType, userId, openId, agentIdOfUserIsVisiting, request);
//                h5IndexUri += "&type=" + REQUEST_SOURCE_SCAN_QRCODE;
//            } else if (StringUtils.isNotBlank(request.getParameter("meetingRoomId"))) {
//                h5IndexUri = RedirectUriUtils.buildH5SubscribeUri(appH5RoomSubscribeUri, corpId,
//                        userType, userId, openId, agentIdOfUserIsVisiting, request);
//            }
//        }

        LOGGER.debug("redirecting to h5 index url: {}", h5IndexUri);

        return ResponseEntity.status(HttpStatus.FOUND).location(URI.create(h5IndexUri))
                .header(X_CORPID_HEADER_NAME, corpId)
                .header(X_USERTYPE_HEADER_NAME, userType)
                .build();
    }

    private Cookie buildCookie(String name, String value) {
        Cookie cookie = new Cookie(name, value);
        cookie.setPath("/");
        cookie.setDomain(COOKIE_DOMAIN_NAME);
        cookie.setHttpOnly(false);
        cookie.setSecure(true);
        cookie.setMaxAge(COOKIE_MAX_AGE);

        return cookie;
    }

    @Autowired
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    @Autowired
    public void setWechatProperties(WechatProperties wechatProperties) {
        this.wechatProperties = wechatProperties;
    }

    @Autowired
    public void setSuiteService(SuiteService suiteService) {
        this.suiteService = suiteService;
    }
}
